/*              Copyright(c) 2008 Dave Sexton              *
 * http://www.codeplex.com/DocProject                      *
 * http://www.codeplex.com/DocProject/Project/License.aspx *
 ***********************************************************/
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.ComponentModel;
using System.Drawing;
using System.Data;

namespace DaveSexton.Sandcastle
{
	internal sealed partial class ResolveExternalLinksComponentEditorControl : UserControl
	{
		#region Private / Protected
		private MessageBoxOptions MessageBoxOptions
		{
			get
			{
				if (ownerForm.RightToLeft == RightToLeft.Yes)
					return MessageBoxOptions.RightAlign | MessageBoxOptions.RtlReading;
				else
					return (MessageBoxOptions) 0;
			}
		}

		private ListViewItem SelectedTopicMappingGroupItem
		{
			get
			{
				return (groupsListView.SelectedIndices.Count > 0)
					? groupsListView.Items[groupsListView.SelectedIndices[0]]
					: null;
			}
		}

		private DataRow SelectedTopicMappingGroup
		{
			get
			{
				ListViewItem item = SelectedTopicMappingGroupItem;

				if (item == null)
					return null;
				else
					return (DataRow) item.Tag;
			}
		}

		private readonly ResolveExternalLinksComponentConfiguration config;
		private readonly Form ownerForm;
		private readonly DataSet mappingsData = CreateMappingsDataStructure(), namespaceData = CreateNamespaceDataStructure();
		private bool ignorefileTextChanged, fileTextChanged;
		#endregion

		#region Constructors
		/// <summary>
		/// Constructs a new instance of the <see cref="ResolveExternalLinksComponentEditorControl" /> class.
		/// </summary>
		internal ResolveExternalLinksComponentEditorControl(string displayName, Form ownerForm, ResolveExternalLinksComponentConfiguration config)
			: this()
		{
			if (ownerForm == null)
				throw new ArgumentNullException("ownerForm");

			if (config == null)
				throw new ArgumentNullException("config");

			this.ownerForm = ownerForm;
			this.config = config;

			if (ownerForm != null)
			{
				ownerForm.Text = displayName + " editor";
				ownerForm.Size = this.Size;
			}

			groupsBindingSource.DataMember = "Groups";
			groupsBindingSource.DataSource = mappingsData;

			namespacesBindingSource.DataMember = "Namespaces";
			namespacesBindingSource.DataSource = namespaceData;
		}

		/// <summary>
		/// Constructs a new instance of the <see cref="ResolveExternalLinksComponentEditorControl" /> class for 
		/// designer support.
		/// </summary>
		private ResolveExternalLinksComponentEditorControl()
		{
			InitializeComponent();

			seeAlsoXPathTextBox.DataBindings.Add("Enabled", seeAlsoXPathCheckBox, "Checked");
		}
		#endregion

		#region Methods
		private static DataSet CreateMappingsDataStructure()
		{
			DataSet data = new DataSet("Component");
			data.Locale = System.Globalization.CultureInfo.CurrentCulture;

			DataTable groups = data.Tables.Add("Groups");
			groups.Columns.Add("base", typeof(Uri));
			groups.Columns.Add("topic", typeof(string));
			groups.Columns.Add("target", typeof(string));
			groups.Columns.Add("file", typeof(string));
			groups.Columns.Add("type", typeof(string));
			groups.Columns.Add("assembly", typeof(string));
			groups.Columns.Add("provider_instance", typeof(UrlMappingProvider));
			groups.Columns.Add("config", typeof(string));
			groups.Columns.Add("original_config", typeof(string));		// used when file is already set to preserve inner config content

			return data;
		}

		private static DataSet CreateNamespaceDataStructure()
		{
			DataSet data = new DataSet("Namespaces");
			data.Locale = System.Globalization.CultureInfo.CurrentCulture;

			DataTable @namespaces = data.Tables.Add("Namespaces");
			DataColumn prefixColumn = @namespaces.Columns.Add("prefix", typeof(string));
			prefixColumn.AllowDBNull = false;
			DataColumn namespacesColumn = @namespaces.Columns.Add("namespace", typeof(string));
			namespacesColumn.AllowDBNull = false;
			@namespaces.PrimaryKey = new[] { prefixColumn, namespacesColumn };

			return data;
		}

		private void Initialize()
		{
			enabledCheckBox.Checked = config.Enabled;
			defaultBaseUrlTextBox.Text = config.BaseUri;
			defaultTargetComboBox.Text = config.Target;

			seeAlsoXPathCheckBox.Checked = config.ReplaceSeeAlsoLinks;
			seeAlsoXPathTextBox.Text = config.SeeAlsoXPath;
			seeXPathTextBox.Text = config.SeeXPath;
			seeLinkXmlTextBox.Text = config.SeeLinkXml;

			LoadNamespaces();
			LoadMappingGroups();
		}

		private void LoadNamespaces()
		{
			DataTable namespaces = namespaceData.Tables["Namespaces"];
			namespaces.Clear();

			foreach (System.Xml.XPath.XPathNavigator navigator in config.Navigator.SelectChildren("context", ""))
			{
				string @namespace = navigator.GetAttribute("name", "");

				if (!string.IsNullOrEmpty(@namespace))
					namespaces.Rows.Add(navigator.GetAttribute("prefix", ""), @namespace);
			}
		}

		private void LoadMappingGroups()
		{
			config.ForEachMappingGroup(delegate(System.Xml.XPath.XPathNavigator mappings)
			{
				AddMappingGroup(mappings.ReadSubtree());
			});

			if (groupsListView.Items.Count > 0)
			{
				groupsListView.SelectedIndices.Clear();
				groupsListView.SelectedIndices.Add(0);
			}
		}

		private ListViewItem AddMappingGroup(System.Xml.XmlReader reader)
		{
			return groupsListView.Items.Add(CreateTopicMappingGroupItem(reader));
		}

		private ListViewItem AddMappingGroup()
		{
			ListViewItem lastItem = null;

			using (UrlMappingProviderDialog form = new UrlMappingProviderDialog())
			{
				if (form.ShowDialog(ownerForm) == DialogResult.OK)
				{
					foreach (KeyValuePair<Type, string> provider in form.SelectedProviderTypes)
						lastItem = groupsListView.Items.Add(CreateTopicMappingGroupItem(provider.Key, provider.Value));
				}
			}

			return lastItem;
		}

		private ListViewItem CreateTopicMappingGroupItem(Type providerType, string providerAssembly)
		{
			DataTable groups = mappingsData.Tables["Groups"];
			DataRow row = groups.NewRow();
			groups.Rows.Add(row);

			row["type"] = (providerType == null) ? null : providerType.FullName;
			row["assembly"] = providerAssembly;

			InstantiateProvider(row, providerType);

			return CreateTopicMappingGroupItem(row);
		}

		private ListViewItem CreateTopicMappingGroupItem(System.Xml.XmlReader reader)
		{
			DataTable groups = mappingsData.Tables["Groups"];
			DataRow row = groups.NewRow();
			groups.Rows.Add(row);

			if (reader != null)
			{
				UrlMappingGroup group = UrlMappingGroup.CreateFrom(config.CurrentDirectory, reader);

				row["base"] = group.BaseUri;
				row["target"] = group.Target;
				row["topic"] = group.TopicId;
				row["file"] = group.File;

				FileUrlMappingProvider fileProvider;

				if ((fileProvider = group.Provider as FileUrlMappingProvider) != null)
				{
					row["type"] = fileProvider.NestedProviderType;
					row["assembly"] = fileProvider.NestedProviderAssembly;
					row["provider_instance"] = group.Provider;
				}
				else
				{
					row["type"] = group.ProviderType;
					row["assembly"] = group.ProviderAssembly;
					row["provider_instance"] = group.Provider;
				}

				System.Xml.XmlDocument document = new System.Xml.XmlDocument();
				document.PreserveWhitespace = true;
				document.InnerXml = group.Configuration;

				if (fileProvider == null)
					row["config"] = document.OuterXml;
				else
				{
					row["original_config"] = document.DocumentElement.InnerXml;

					string groupConfig = fileProvider.ConfigurationFromFile;

					if (!string.IsNullOrEmpty(groupConfig))
					{
						document.LoadXml(groupConfig);

						row["config"] = document.OuterXml;
					}
				}
			}

			return CreateTopicMappingGroupItem(row);
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
		private void InstantiateProvider(DataRow row, Type providerType)
		{
			if (providerType == null)
				row["provider_instance"] = XmlUrlMappingProvider.Instance;
			else
			{
				try
				{
					row["provider_instance"] = UrlMappingProvider.GetProvider(providerType);
				}
				catch (Exception ex)
				{
					MessageBox.Show(ownerForm, string.Format(System.Globalization.CultureInfo.CurrentCulture,
						Resources.Errors.ErrorCreatingUrlMappingProviderInstance, Environment.NewLine, providerType.AssemblyQualifiedName, ex.Message),
						Resources.Text.ErrorCreatingUrlMappingProviderInstanceMessageCaption,
						MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1, MessageBoxOptions);
				}
			}
		}

		private static ListViewItem CreateTopicMappingGroupItem(DataRow row)
		{
			ListViewItem item = new ListViewItem();
			item.Tag = row;
			item.Text = GetProviderDisplayName(row);

			return item;
		}

		private static string GetProviderDisplayName(DataRow row)
		{
			if (!row.IsNull("provider_instance"))
			{
				UrlMappingProvider provider = (UrlMappingProvider) row["provider_instance"];
				FileUrlMappingProvider fileProvider = provider as FileUrlMappingProvider;

				if (fileProvider == null)
					return ComponentDescriptor.GetDisplayName(provider.GetType());
				else
					return ComponentDescriptor.GetDisplayName(fileProvider.NestedProvider.GetType());
			}
			else if (!row.IsNull("type"))
			{
				string type = (string) row["type"];

				StringBuilder text = new StringBuilder();

				int period = type.LastIndexOf('.') + 1;

				text.Append(type, period, type.Length - period);
				text.Append(" (" + type);

				if (!row.IsNull("assembly"))
					text.Append(", " + row["assembly"]);

				text.Append(")");

				return text.ToString();
			}
			else
				return Resources.Text.XmlMappingGroup;
		}

		private void CommitChanges()
		{
			bool valid = true;

			string seeLinkXml = seeLinkXmlTextBox.Text.Trim();
			string seeXPath = seeXPathTextBox.Text;
			string seeAlsoXPath = seeAlsoXPathTextBox.Text;

			if (!string.IsNullOrEmpty(seeLinkXml) && !ValidateXml(seeLinkXml, false, seeLinkXmlTextBox))
			{
				tabs.SelectedTab = generalTabPage;
				valid = false;
			}

			System.Xml.XmlNamespaceManager ns = CreateXmlNamespaceManager();

			if (!ValidateXPath(seeXPath, ns, seeXPathTextBox))
			{
				tabs.SelectedTab = generalTabPage;
				valid = false;
			}

			if (!string.IsNullOrEmpty(seeAlsoXPath) && !ValidateXPath(seeAlsoXPath, ns, seeAlsoXPathTextBox))
			{
				tabs.SelectedTab = generalTabPage;
				valid = false;
			}

			if (!ValidateProvidersConfig())
				valid = false;

			if (valid)
			{
				config.Enabled = enabledCheckBox.Checked;

				string @base = defaultBaseUrlTextBox.Text.Trim();
				string target = defaultTargetComboBox.Text.Trim();

				if (@base.Length == 0)
					config.BaseUri = null;
				else
					config.BaseUri = @base;

				if (target.Length == 0)
					config.Target = null;
				else
					config.Target = target;

				config.ReplaceSeeAlsoLinks = seeAlsoXPathCheckBox.Checked && !string.IsNullOrEmpty(seeAlsoXPath);
				config.SeeAlsoXPath = seeAlsoXPath;
				config.SeeXPath = seeXPath;
				config.SeeLinkXml = seeLinkXml;

				List<System.Xml.XPath.XPathNavigator> deleteNodes = new List<System.Xml.XPath.XPathNavigator>();

				foreach (System.Xml.XPath.XPathNavigator navigator in config.Navigator.SelectChildren("context", ""))
					deleteNodes.Add(navigator);

				config.ForEachMappingGroup((m) => deleteNodes.Add(m));

				foreach (System.Xml.XPath.XPathNavigator navigator in deleteNodes)
					navigator.DeleteSelf();

				SaveNamespaces(config.Navigator);
				SaveMappings(config.Navigator);

				ownerForm.DialogResult = DialogResult.OK;
			}
		}

		private System.Xml.XmlNamespaceManager CreateXmlNamespaceManager()
		{
			System.Xml.XmlNamespaceManager ns = new System.Xml.XmlNamespaceManager(new System.Xml.NameTable());

			foreach (DataRow row in namespaceData.Tables["Namespaces"].Rows)
				ns.AddNamespace((string) row["Prefix"], (string) row["Namespace"]);

			return ns;
		}

		private void SaveNamespaces(System.Xml.XPath.IXPathNavigable navigable)
		{
			System.Xml.XPath.XPathNavigator navigator = navigable.CreateNavigator();

			using (System.Xml.XmlWriter writer = navigator.AppendChild())
			{
				foreach (DataRow row in namespaceData.Tables["Namespaces"].Rows)
				{
					string prefix = (string) row["prefix"];
					string @namespace = (string) row["namespace"];

					writer.WriteStartElement("context");
					writer.WriteAttributeString("prefix", prefix);
					writer.WriteAttributeString("name", @namespace);
					writer.WriteEndElement();
				}
			}
		}

		private void SaveMappings(System.Xml.XPath.IXPathNavigable navigable)
		{
			System.Xml.XPath.XPathNavigator navigator = navigable.CreateNavigator();

			using (System.Xml.XmlWriter writer = navigator.AppendChild())
			{
				foreach (DataRow group in mappingsData.Tables["Groups"].Rows)
				{
					Uri @base = (group.IsNull("base")) ? null : (Uri) group["base"];
					string topic = (group.IsNull("topic")) ? null : (string) group["topic"];
					string target = (group.IsNull("target")) ? null : (string) group["target"];
					string file = (group.IsNull("file")) ? null : (string) group["file"];
					string type = (group.IsNull("type")) ? null : (string) group["type"];
					string assembly = (group.IsNull("assembly")) ? null : (string) group["assembly"];
					string configXml = (group.IsNull("config")) ? null : (string) group["config"];

					writer.WriteStartElement("mappings");

					if (@base != null)
						writer.WriteAttributeString("base", @base.ToString());

					if (!string.IsNullOrEmpty(topic))
						writer.WriteAttributeString("topic", topic);

					if (!string.IsNullOrEmpty(target))
						writer.WriteAttributeString("target", target);

					if (!string.IsNullOrEmpty(type))
					{
						writer.WriteAttributeString("type", type);

						if (!string.IsNullOrEmpty(assembly))
							writer.WriteAttributeString("assembly", assembly);
					}

					System.Xml.XmlDocument configDocument = null;

					if (configXml != null)
					{
						configDocument = new System.Xml.XmlDocument();
						configDocument.PreserveWhitespace = true;
						configDocument.LoadXml(configXml);
					}

					if (string.IsNullOrEmpty(file))
					{
						if (configDocument != null)
						{
							System.Xml.XPath.XPathNavigator configNav = configDocument.DocumentElement.CreateNavigator();
							configNav.MoveToChild(System.Xml.XPath.XPathNodeType.All);

							do
							{
								writer.WriteNode(configNav, false);
							}
							while (configNav.MoveToNext(System.Xml.XPath.XPathNodeType.All));
						}
					}
					else
					{
						writer.WriteAttributeString("file", file);

						if (configDocument != null)
						{
							if (config.Host != null)
								config.Host.CheckoutItemInProject(file);

							file = FileUrlMappingProvider.FormatFilePath(config.CurrentDirectory, file);

							configDocument.Save(file);

							if (config.Host != null)
								config.Host.IncludeItemInProject(file);

							configXml = (group.IsNull("original_config")) ? null : (string) group["original_config"];

							if (!string.IsNullOrEmpty(configXml))
								writer.WriteRaw(configXml);
						}
					}

					writer.WriteEndElement();
				}
			}
		}

		private bool ValidateProvidersConfig()
		{
			DataTable table = mappingsData.Tables["Groups"];

			foreach (DataRow group in table.Rows)
			{
				if (!group.IsNull("type") && !group.IsNull("config"))
				{
					string groupConfig = (string) group["config"];

					if (!string.IsNullOrEmpty(groupConfig) && !ValidateXml(groupConfig, true, null))
					{
						int index = table.Rows.IndexOf(group);

						if (groupsListView.SelectedIndices.Count == 0 || groupsListView.SelectedIndices[0] != index)
						{
							groupsListView.SelectedIndices.Clear();
							groupsListView.SelectedIndices.Add(index);
						}

						tabs.SelectedTab = mappingsTabPage;

						ShowDefaultEditor();

						return false;
					}
				}
			}

			return true;
		}

		[System.Diagnostics.DebuggerHidden]
		private bool ValidateXml(string xml, bool hasRoot, Control errorHost)
		{
			System.Xml.XmlDocument document = new System.Xml.XmlDocument();

			try
			{
				if (!hasRoot)
				{
					System.Xml.XmlNode root = document.AppendChild(document.CreateElement("root"));
					root.InnerXml = xml;
				}
				else
					document.LoadXml(xml);

				errors.SetError(errorHost ?? okButton, null);
				return true;
			}
			catch (System.Xml.XmlException ex)
			{
				errors.SetError(errorHost ?? okButton, string.Format(System.Globalization.CultureInfo.CurrentCulture,
					Resources.Errors.InvalidProviderConfigXml, Environment.NewLine, ex.Message));

				return false;
			}
		}

		[System.Diagnostics.DebuggerHidden]
		private bool ValidateXPath(string xpath, System.Xml.IXmlNamespaceResolver resolver, Control errorHost)
		{
			try
			{
				System.Xml.XPath.XPathExpression.Compile(xpath, resolver);
				errors.SetError(errorHost ?? okButton, null);
				return true;
			}
			catch (ArgumentException ex)
			{
				errors.SetError(errorHost ?? okButton, string.Format(System.Globalization.CultureInfo.CurrentCulture,
					Resources.Errors.InvalidProviderConfigXPath, Environment.NewLine, ex.Message));
			}
			catch (System.Xml.XPath.XPathException ex)
			{
				errors.SetError(errorHost ?? okButton, string.Format(System.Globalization.CultureInfo.CurrentCulture,
					Resources.Errors.InvalidProviderConfigXPath, Environment.NewLine, ex.Message));
			}

			return false;
		}

		private void SyncBindings()
		{
			SuspendLayout();

			try
			{
				ignorefileTextChanged = true;

				bool hasSelection = groupsListView.SelectedIndices.Count > 0;

				settingsContainer.Enabled = hasSelection;
				removeGroupToolStripButton.Enabled = hasSelection;

				groupsBindingSource.Position = (hasSelection) ? groupsListView.SelectedIndices[0] : groupsBindingSource.Count;

				if (hasSelection)
				{
					groupsBindingSource.Filter = null;
					groupsBindingSource.Position = groupsListView.SelectedIndices[0];

					DataRow group = ((DataRowView) groupsBindingSource.Current).Row;

					SyncConfigurationEditor(group);
				}
				else
				{
					ShowEditor(null);

					groupsBindingSource.Filter = "1=0";
				}
			}
			finally
			{
				ignorefileTextChanged = false;
				ResumeLayout();
			}
		}

		private void ShowDefaultEditor()
		{
			ShowEditor(providerConfigTextBox);
		}

		private void ShowEditor(Control editor)
		{
			if (settingsContainer.Panel2.Controls.Count > 0)
			{
				Control existingEditor = settingsContainer.Panel2.Controls[0];

				if (existingEditor == editor)
					return;
				else if (existingEditor != null && existingEditor != providerConfigTextBox)
					existingEditor.Leave -= editor_Leave;

				settingsContainer.Panel2.Controls.Clear();
			}

			if (editor != null)
			{
				editor.Leave += editor_Leave;
				editor.Dock = DockStyle.Fill;

				settingsContainer.Panel2.Controls.Add(editor);

				if (editor.CanFocus)
					editor.Focus();
			}
		}

		private void SyncConfigurationEditor(DataRow group)
		{
			Control editor = null;

			if (group != null)
			{
				UrlMappingProvider provider = GetProvider(group);

				if (provider != null)
				{
					FileUrlMappingProvider file = provider as FileUrlMappingProvider;

					if (file == null)
					{
						string groupConfig = (group.IsNull("config")) ? string.Empty : (string) group["config"];

						if (!string.IsNullOrEmpty(groupConfig))
						{
							System.Xml.XmlDocument configDocument = new System.Xml.XmlDocument();
							configDocument.PreserveWhitespace = true;
							configDocument.LoadXml(groupConfig);
							groupConfig = configDocument.DocumentElement.InnerXml;
						}

						editor = provider.GetEditor(CreateUrlMappingGroup(group), groupConfig);
					}
					else
					{
						if (!group.IsNull("config"))
							file.ConfigurationFromFile = (string) group["config"];

						editor = provider.GetEditor(CreateUrlMappingGroup(group), null);
					}
				}

				if (editor == null)
					editor = providerConfigTextBox;
			}

			ShowEditor(editor);
		}

		private void ReadEditorConfiguration()
		{
			DataRow group = SelectedTopicMappingGroup;

			if (group != null)
			{
				UrlMappingProvider provider = GetProvider(group);

				System.Xml.XmlDocument configDocument = new System.Xml.XmlDocument();
				configDocument.PreserveWhitespace = true;

				if (group.IsNull("config"))
					configDocument.AppendChild(configDocument.CreateElement("config"));
				else
					configDocument.LoadXml((string) group["config"]);

				configDocument.DocumentElement.InnerXml = 
					Environment.NewLine + 
					provider.GetEditorConfiguration(CreateUrlMappingGroup(group), settingsContainer.Panel2.Controls[0]) +
					Environment.NewLine;

				group["config"] = configDocument.OuterXml;
			}
		}

		private static UrlMappingGroup CreateUrlMappingGroup(DataRow group)
		{
			return new UrlMappingGroup(
				(group.IsNull("base")) ? null : (Uri) group["base"],
				(group.IsNull("target")) ? null : (string) group["target"],
				(group.IsNull("topic")) ? null : (string) group["topic"],
				(group.IsNull("file")) ? null : (string) group["file"],
				(group.IsNull("provider_instance")) ? null : (UrlMappingProvider) group["provider_instance"],
				(group.IsNull("type")) ? null : (string) group["type"],
				(group.IsNull("assembly")) ? null : (string) group["assembly"],
				(group.IsNull("config")) ? null : (string) group["config"]);
		}

		private UrlMappingProvider GetProvider(DataRow group)
		{
			UrlMappingProvider provider = null;

			if (!group.IsNull("provider_instance"))
			{
				provider = (UrlMappingProvider) group["provider_instance"];

				FileUrlMappingProvider fileProvider = provider as FileUrlMappingProvider;

				if (group.IsNull("file") || ((string) group["file"]).Trim().Length == 0)
				{
					if (fileProvider != null)
					// a file mapping provider cannot be used since a file is no longer specified on the mapping group
					{
						group["provider_instance"] = provider = fileProvider.NestedProvider;

						if (group.IsNull("config"))
							group["config"] = fileProvider.ConfigurationFromFile;
					}
				}
				else if (fileProvider == null || !string.Equals(fileProvider.File, (string) group["file"], StringComparison.OrdinalIgnoreCase))
				// a file mapping provider must be used since a file is specified on the mapping group
				{
					if (group.IsNull("type"))
						group["provider_instance"] = provider = new FileUrlMappingProvider(config.CurrentDirectory, (string) group["file"],
							(group.IsNull("config")) ? null : (string) group["config"]);
					else
						group["provider_instance"] = provider = new FileUrlMappingProvider(config.CurrentDirectory, (string) group["file"],
							(string) group["type"], (group.IsNull("assembly")) ? null : (string) group["assembly"],
							(group.IsNull("config")) ? null : (string) group["config"]);
				}
				else if (group.IsNull("config"))
					group["config"] = fileProvider.ConfigurationFromFile;
			}
			else if (!group.IsNull("file"))
				provider = new FileUrlMappingProvider(config.CurrentDirectory, (string) group["file"],
					(group.IsNull("config")) ? null : (string) group["config"]);

			return provider;
		}

		private void BrowseForFile()
		{
			DataRow group = SelectedTopicMappingGroup;

			if (group == null)
				return;

			using (OpenFileDialog dialog = new OpenFileDialog())
			{
				string file = (group.IsNull("file")) ? null : (string) group["file"];

				if (!string.IsNullOrEmpty(file) && !System.IO.Path.IsPathRooted(file))
					file = System.IO.Path.GetFullPath(System.IO.Path.Combine(config.CurrentDirectory, file));

				dialog.Title = "Open or create a new mappings configuration file";
				dialog.Filter = "XML files (*.xml)|*.xml|All files (*.*)|*.*";
				dialog.FileName = System.IO.Path.GetFileName(file);
				dialog.CheckFileExists = false;
				dialog.CheckPathExists = true;
				dialog.DefaultExt = ".xml";
				dialog.AddExtension = true;

				if (string.IsNullOrEmpty(file))
				{
					if (config.SettingsDirectory != null)
						dialog.InitialDirectory = config.SettingsDirectory;
				}
				else
				{
					file = Environment.ExpandEnvironmentVariables(file);

					if (System.IO.Path.IsPathRooted(file))
						dialog.InitialDirectory = System.IO.Path.GetDirectoryName(file);
					else
						dialog.InitialDirectory = System.IO.Path.GetFullPath(System.IO.Path.Combine(config.CurrentDirectory, System.IO.Path.GetDirectoryName(file)));
				}

				if (dialog.ShowDialog(ownerForm) == DialogResult.OK)
				{
					group["file"] = MakeRelative(config.CurrentDirectory, dialog.FileName);

					fileTextBox.DataBindings[0].ReadValue();

					fileTextBox.Focus();
					fileTextBox.SelectAll();

					SyncConfigurationEditor(group);
				}
			}
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1307:SpecifyStringComparison", MessageId = "System.String.EndsWith(System.String)")]
		private static string MakeRelative(string rootPath, string targetPath)
		{
			if (!rootPath.EndsWith("/") && !rootPath.EndsWith("\\"))
				rootPath += "\\";

			Uri root;
			if (Uri.TryCreate(rootPath, UriKind.Absolute, out root))
			{
				Uri target;
				if (Uri.TryCreate(targetPath, UriKind.RelativeOrAbsolute, out target))
				{
					if (target.IsAbsoluteUri && (target.IsFile || target.IsUnc))
					{
						target = root.MakeRelativeUri(target);

						// NOTE: Relative uri must be decoded since spaces are escaped.  Also, if the uri returned
						// is absolute the IsAbsoluteUri property will still be false and the ToString() method will
						// behave as a relative uri instead of an absolute uri (so the scheme is not returned).
						return System.Web.HttpUtility.UrlDecode(target.ToString()).Replace('/', '\\');
					}
				}
			}

			return targetPath;
		}
		#endregion

		#region Event Handlers
		private void TestAPIComponentEditorControl_Load(object sender, EventArgs e)
		{
			Initialize();
		}

		private void okButton_Click(object sender, EventArgs e)
		{
			CommitChanges();
		}

		private void groupsListView_SelectedIndexChanged(object sender, EventArgs e)
		{
			SyncBindings();
		}

		private void editor_Leave(object sender, EventArgs e)
		{
			ReadEditorConfiguration();
		}

		private void removeGroupToolStripButton_Click(object sender, EventArgs e)
		{
			ListViewItem item = SelectedTopicMappingGroupItem;

			if (item != null)
			{
				int index = item.Index;

				groupsListView.Items.Remove(item);
				mappingsData.Tables["Groups"].Rows[index].Delete();

				if (index < groupsListView.Items.Count)
					groupsListView.SelectedIndices.Add(index);
				else if (groupsListView.Items.Count > 0)
					groupsListView.SelectedIndices.Add(groupsListView.Items.Count - 1);
			}
		}

		private void addGroupToolStripButton_Click(object sender, EventArgs e)
		{
			ListViewItem item = AddMappingGroup();

			if (item != null)
				item.Selected = true;
		}

		private void fileOpenButton_Click(object sender, EventArgs e)
		{
			BrowseForFile();
		}

		private void fileTextBox_TextChanged(object sender, EventArgs e)
		{
			if (!ignorefileTextChanged)
				fileTextChanged = true;
		}

		private void fileTextBox_Leave(object sender, EventArgs e)
		{
			if (fileTextChanged)
			{
				fileTextBox.DataBindings[0].WriteValue();

				SyncConfigurationEditor(SelectedTopicMappingGroup);
				fileTextChanged = false;
			}
		}

		private void topicIdComboBox_DropDown(object sender, EventArgs e)
		{
			if (topicIdComboBox.DataSource == null)
			{
				ApiReferenceCollection references = config.ApiReferences;

				if (references != null)
				{
					topicIdComboBox.DataSource = new BindingList<ApiReference>(new List<ApiReference>(references));

					// for some unknown reason this property must be set after DataSource otherwise it automatically resets to an empty string
					topicIdComboBox.DisplayMember = "TopicId";
					topicIdComboBox.AutoCompleteSource = AutoCompleteSource.ListItems;
				}
			}
		}
		#endregion
	}
}
